import React, { useRef, useState, useEffect } from 'react';
import {
  useMemoizedFn,
  useDebounceFn,
  useUpdateEffect,
  useUnmount,
} from 'ahooks';
import { Graph, Cell, Node, Edge } from '@antv/x6';
import { Dropdown, Menu } from 'antd';
import { getLaneStyle, nextTick } from '../../tools';

import {
  getGraph,
  registerLane,
  registerResizeNode,
  registerNodeMenuContext,
  registerResizeLane,
  registerEdgeRemove,
  registerNodeNumChange,
  registerAutoSave,
  registerHistory,
  registerLaneCellChange,
  registerKeyboard,
  registerDbClickNode,
  getNodeAttr,
  registerDragGraph,
} from './tools';
import { GraphData } from '../../types';

let unListenerDragGraph: any;

interface LaneProps {
  data: GraphData;
  zoom?: number;
  isFullScreen?: boolean;
  onActions?: (action: string) => void;
  onLoad: (graph: any) => void;
  assets: { [key: string]: string };
  setCurrentCell: (cell: any) => void;
  setCanRedo: (canRedo: boolean) => void;
  setCanUndo: (canUndo: boolean) => void;
  onUndoOrRedo?: (
    type: 'undo' | 'redo',
    graph: Graph,
    args: { cmds: any[]; options: any },
  ) => void;
  defaultEdgeShape?: string;
}
const Lane: React.FC<LaneProps> = function ({
  data,
  onActions,
  onLoad,
  assets,
  setCurrentCell,
  zoom = 1,
  isFullScreen,
  setCanRedo,
  setCanUndo,
  onUndoOrRedo,
  defaultEdgeShape,
}) {
  const containerRef = useRef<HTMLDivElement>(null);
  const laneRef = useRef<HTMLDivElement>(null);
  const laneTitleRef = useRef<SVGSVGElement>(null);
  const laneWrapperRef = useRef<HTMLDivElement>(null);
  const miniMapRef = useRef<HTMLDivElement>(null);
  const menuRef = useRef<HTMLDivElement>(null);
  const [graph, setGraph] = useState<Graph>();

  useEffect(() => {
    if (!graph) return;
    const nodes = graph.model.getNodes();
    if (Array.isArray(nodes)) {
      nodes.forEach((n) => {
        const { type } = n.data;
        if (type === 'node') {
          n.attr(getNodeAttr(n.shape, n.data, assets));
        }
      });
    }
  }, [assets, graph]);

  // 画布事件响应
  const handlerActions = useMemoizedFn((action: string) => {
    onActions && onActions(action);
    onVisibleChange(false);
  });
  // 删除连接 防抖
  const { run: onRemoveEdge } = useDebounceFn(
    (edge: any) => {
      if (!edge.data) return;
      setCurrentCell(edge);
      nextTick(() => handlerActions('delete'));
    },
    { wait: 100 },
  );

  const syncLaneTitle = useMemoizedFn(() => {
    const x6Graph = containerRef.current;
    const laneTitleSvg = laneTitleRef.current;
    if (!x6Graph || !laneTitleSvg) return;
    // const x6GraphParent = x6Graph.parentNode;
    const x6GraphLaneShapes = x6Graph.querySelectorAll('g[data-shape="lane"]');
    const laneTitleStage = laneTitleSvg.querySelector(
      '.x6-graph-svg-stage',
    ) as SVGSVGElement;
    laneTitleStage.innerHTML = '';
    Array.from(x6GraphLaneShapes).forEach((laneShapeNode) => {
      const laneTitleLaneShape = laneShapeNode.cloneNode(true);
      Array.from(laneTitleLaneShape.childNodes).forEach((node: any) => {
        const className = node.getAttribute('class');
        if (className === 'laneBody') {
          laneTitleLaneShape.removeChild(node);
        } else if (className === 'resizeBorder') {
          node.setAttribute('width', '30');
        }
      });
      laneTitleStage.appendChild(laneTitleLaneShape);
    });
    syncContainerSize();

    x6Graph.style.transformOrigin = 'left top';
    x6Graph.style.transform = `scale(${zoom})`;
    laneTitleSvg.style.transformOrigin = 'left top';
    laneTitleSvg.style.height = x6Graph.offsetHeight + 'px';
    laneTitleSvg.style.transform = `scale(${zoom})`;
  });

  // 同步画布容器的尺寸
  const syncContainerSize = useMemoizedFn(() => {
    if (!graph || !laneWrapperRef.current || !containerRef.current) return;
    const laneNodeList = graph
      .getNodes()
      .filter((node) => node.shape === 'lane');

    const maxHeight = Math.max(
      (laneWrapperRef.current.parentNode as any).clientHeight || 0,
      laneNodeList.reduce((p, item) => p + item.size().height, 0) + 50,
    );
    containerRef.current.style.height = maxHeight + 'px';
    if (!containerRef.current.parentNode) return;
    (containerRef.current.parentNode as any).style.height =
      containerRef.current.offsetHeight * zoom + 'px';
  });

  // 监控缩放
  useEffect(() => {
    // 当数据发生变化时，需要等画布发生变化后再去同步节点才能准确
    nextTick(() => {
      const x6Graph = containerRef.current;
      if (!graph || !x6Graph) return;
      syncLaneTitle();
      const x6GraphParent = x6Graph.parentNode;
      if (!x6GraphParent) return;
      (x6GraphParent as any).style.height = x6Graph.offsetHeight * zoom + 'px';
    });
  }, [zoom, graph, data]);

  const handleMenuClick = ({ key }: any) => {
    handlerActions(key);
  };
  // 改变泳道高度
  const onLaneReszie = useMemoizedFn(
    (laneNode: any, width: number, height: number, minHeight: number) => {
      if (height < minHeight || !graph) return;
      else laneNode.size(width, height);
      const laneNodeList = graph
        .getNodes()
        .filter((node) => node.shape === 'lane');

      const laneIndex = laneNodeList.findIndex(
        (lane) => lane.id === laneNode.id,
      );
      if (laneIndex > -1) {
        // 改变当前泳道的高度
        let currentY = laneNode.position().y + height;
        // 调整泳道内节点的Y坐标
        const children = laneNode.getChildren();
        if (Array.isArray(children) && children.length) {
          children.forEach((child: Node) => {
            const { height: ch } = child.size();
            const { x: cx, y: cy } = child.position();
            if (cy + ch > currentY) {
              child.position(cx, currentY - ch);
            }
          });
        }
        // 调整位置在当前泳道下方的泳道的位置
        laneNodeList.slice(laneIndex + 1).forEach((nextLaneNode) => {
          const { x: laneX, y: laneY } = nextLaneNode.position();
          nextLaneNode.position(laneX, currentY);
          // 调整泳道位置的同时也要调整泳道内子节点的位置
          const children = nextLaneNode.getChildren() as Node[];
          if (Array.isArray(children) && children.length > 0) {
            children.forEach((childNode) => {
              const { x: childX, y: childY } = childNode.position();
              const offsetY = childY - laneY;
              childNode.position(childX, currentY + offsetY);
            });
          }
          currentY += nextLaneNode.size().height;
        });
      }
    },
  );
  // 添加连接
  const onAddLink = (edge: Edge) => {
    setCurrentCell(edge);
    nextTick(() => handlerActions('add'));
  };

  const initPage = useMemoizedFn(() => {
    if (
      !containerRef.current ||
      !laneWrapperRef.current ||
      !laneWrapperRef.current
      // !miniMapRef.current
    ) {
      return;
    }
    let gh = graph;
    if (!gh) {
      registerLane({
        width: containerRef.current.clientWidth,
        height: 200,
      });

      gh = getGraph(containerRef.current, miniMapRef.current, {
        onAddLink,
        defaultEdgeShape,
      });
      registerNodeMenuContext(gh, menuRef, setCurrentCell);
      registerResizeNode(gh);
      registerResizeLane(gh, onLaneReszie);
      registerEdgeRemove(gh, onRemoveEdge);
      registerNodeNumChange(gh);
      registerAutoSave(gh);
      registerHistory(gh, { setCanRedo, setCanUndo, onUndoOrRedo });
      registerKeyboard(gh, onActions);
      registerDbClickNode(gh, setCurrentCell, onActions);
      unListenerDragGraph = registerDragGraph(gh, laneRef, laneWrapperRef);
    } else {
      gh.clearCells();
    }
    const cells: Cell[] = [];
    const {
      laneList: laneDataList, // 泳道数据
      nodeList: nodeDataList, // 节点数据
      linkList: linkDataList, // 连线数据
      width,
      height,
    } = data;
    // 计算页面宽高，并将宽度赋值给泳道，并在泳道的上下留出一定的空间方便拖拽
    const maxWidth = Math.max(containerRef.current.clientWidth, width || 0);
    const maxHeight = Math.max(
      (laneWrapperRef.current.parentNode as any).clientHeight,
      height || 0,
    );
    containerRef.current.style.width = maxWidth + 'px';
    containerRef.current.style.height = maxHeight + 'px';
    laneDataList.forEach((item) => {
      item.width = maxWidth;
      if (!item.attrs) item.attrs = getLaneStyle(Number(item.id) - 1);
    });
    // 泳道map方便查找
    const laneMap: any = {};
    // 泳道图形化
    laneDataList.forEach((item) => {
      const lane = gh!.createNode(item);
      registerLaneCellChange(lane, () => nextTick(() => syncLaneTitle()));
      cells.push(lane);
      laneMap[lane.id] = lane;
    });
    // 节点图形化
    const nodeInLaneMap = {};
    nodeDataList.forEach((item) => {
      const { preId } = item.data || {};
      const node = gh!.createNode(item);
      node.attr(getNodeAttr(item.shape, item.data, assets));
      cells.push(node);
      if (!nodeInLaneMap[preId]) nodeInLaneMap[preId] = [];
      nodeInLaneMap[preId].push(node);
    });
    // 连线图形化
    linkDataList.forEach((item) => {
      const link = gh!.createEdge(item as any);
      cells.push(link);
    });
    gh.resetCells(cells);
    Object.keys(nodeInLaneMap).forEach((laneId) => {
      const lane = laneMap[laneId];
      const nodeList = nodeInLaneMap[laneId];
      lane.setChildren(nodeList);
    });
    gh.positionRect(
      { x: 0, y: 0, width: maxWidth, height: maxHeight },
      'top-left',
    );
    setGraph(gh);
    onLoad(gh);
    syncLaneTitle();
    gh.cleanHistory();
  });

  useEffect(() => {
    initPage();
  }, [data]);
  useUpdateEffect(() => {
    setTimeout(() => {
      initPage();
    }, 500);
  }, [isFullScreen]);

  useUnmount(() => {
    unListenerDragGraph && unListenerDragGraph();
  });

  const menu = (
    <Menu
      onClick={handleMenuClick}
      items={[
        {
          label: '编辑',
          key: 'edite',
        },
        {
          label: '删除',
          key: 'delete',
        },
      ]}
    ></Menu>
  );

  const onVisibleChange = (visible: boolean) => {
    if (!visible) {
      setTimeout(() => {
        if (!menuRef.current) return;
        menuRef.current.style.left = '-100px';
        menuRef.current.style.top = '-100px';
      }, 100);
    }
  };

  return (
    <div className="ra-arch-laneWrapper" ref={laneWrapperRef}>
      <Dropdown
        overlay={menu}
        placement="bottomLeft"
        onVisibleChange={onVisibleChange}
      >
        <div
          style={{
            position: 'absolute',
            width: 10,
            height: 10,
            background: 'transparent',
            left: -100,
            top: -100,
            zIndex: 999,
          }}
          ref={menuRef}
        ></div>
      </Dropdown>
      <div className="ra-arch-mimiMap" ref={miniMapRef}></div>
      <div className="ra-arch-lane" ref={laneRef}>
        <div ref={containerRef}></div>
        <svg
          width={30}
          style={{ left: 0, top: 0 }}
          height="100%"
          ref={laneTitleRef}
          className="x6-graph-svg"
          xmlns="http://www.w3.org/2000/svg"
        >
          <g className="x6-graph-svg-viewport">
            <g className="x6-graph-svg-stage"></g>
          </g>
        </svg>
      </div>
    </div>
  );
};

export default React.memo(Lane);
